import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

import mastermind.socket.protocol;
import mastermind.database.*;

/**
 * Handles client I/O and communicates with class Game to provide
 * basic connection and socket logic
 * 
 * @author Brendan
 *
 */
public class ServerThread extends Thread{

	private Socket socket = null;
	private StartServer server;
	private DataInputStream is;
	private DataOutputStream os;
    private Thread thread = null;
    private ServerThread client = null;
    private boolean test = true;
    private ServerThread.status mode;
    private int guessattempts;
	private TextDatabase database;
	
    enum status {
    	NOTAUTHENTICATED, LOGIN_SUCCESS, REGISTER_SUCCESS, INGAME, GAMEFINISHED, ERROR
    };
    enum errors {
    	LOGINERROR, REGISTERERROR, DEADSOCKET, INVALIDCODE, GENERICERROR
    }

    /**
     * Exception class that returns meaningful data on failure of server.
     * @author Brendan
     *
     */
    private class ServerException extends Exception {
    	/**
		 * 
		 */
		private static final long serialVersionUID = 1645635L;
		ServerThread.errors theerror;
    	ServerException(ServerThread.errors errormode) {
    		super("Generic Error");
    		theerror = errormode;
    	}
    	@SuppressWarnings("unused")
		ServerException(String msg,ServerThread.errors errormode) {
    		super(msg);
    		theerror = errormode;
    	}
    	@SuppressWarnings("unused")
		ServerThread.errors getErrorMode() {
    		return theerror;
    	}
    }
    
    /**
     * Constructor to create instance, lies in wait until activated.
     * @param server Parent StartServer object 
     * @param socket Socket to operate on
     * @param database TextDatabase object to use as backing store.
     */
	public ServerThread(StartServer server, Socket socket, TextDatabase database)
	{
		this.server = server;
		this.socket = socket;
		guessattempts = 0;
		mode = status.NOTAUTHENTICATED;
		this.database = database;
	}
	
	/**
	 * Attempt to login in, details are validated against server backing store. 
	 * @throws ServerException
	 */
	public synchronized void attemptLogin() throws ServerException {
		try {
			os.writeInt(protocol.SEND_LOGIN_INFO);
			String userName = is.readUTF();
			System.out.println("Username: " + userName);
			String password = is.readUTF();
			System.out.println("Password: " + password);
			if(database.validCredentials(userName, password) == true) {
				os.writeInt(protocol.LOGIN_SUCCESS);
				os.writeInt(protocol.LOGIN_END);
				mode = status.LOGIN_SUCCESS;
			} else {
				os.writeInt(protocol.LOGIN_FAILED);
				os.writeInt(protocol.LOGIN_END);
				mode = status.ERROR;
				throw new ServerException(errors.LOGINERROR);
			}
		} catch (IOException e) {
			e.printStackTrace();
			mode = status.ERROR;
			throw new ServerException(errors.LOGINERROR);
		}
	}
	
	/**
	 * Attempt to register, checks for existing username clashes.
	 * @throws ServerException
	 */
	public synchronized void attemptRegister() throws ServerException {
		try {
			os.writeInt(protocol.SEND_REGISTER_INFO);
			String firstName = is.readUTF();
			System.out.println("First Name: " + firstName);
			String lastName = is.readUTF();
			System.out.println("Last Name: " + lastName);
			String userName = is.readUTF();
			System.out.println("Username: " + userName);
			String password = is.readUTF();
			System.out.println("Password: " + password);
			String phoneNumber = is.readUTF();
			System.out.println("Phone Number: " + phoneNumber);
			if(database.userExists(userName)) {
				os.writeInt(protocol.REGISTER_FAILED);
				os.writeInt(protocol.REGISTER_END);
				mode = status.ERROR;
				throw new ServerException(errors.REGISTERERROR);
			} else {
				database.regiser(firstName, lastName, userName, password, phoneNumber);
				os.writeInt(protocol.REGISTER_SUCCESS);
				os.writeInt(protocol.REGISTER_END);
				mode = status.REGISTER_SUCCESS;
			}
		} catch (IOException e) {
			e.printStackTrace();
			mode = status.ERROR;
			throw new ServerException(errors.REGISTERERROR);
		}
	}
	
	/**
	 * Run Game loop. Main functionality is here.
	 */
	public synchronized void runGame() {
			mode = status.INGAME;
			boolean breakflag = false;
			Game theGame = new Game();

			try {
				// make a new instance of Game here and loop, taking each guess. parsing could
				// be a bit better
				os.writeInt(protocol.GAME_STARTED);
				
				while(true) {
					os.writeInt(protocol.TURN_START);
					String guess = is.readUTF();
					theGame.guess(guess);
					os.writeInt(protocol.TURN_COUNT);
					os.writeInt(theGame.getGuesses());
					os.writeInt(protocol.SEND_FEEDBACK);
					os.writeUTF(theGame.getFeedback());
					if(theGame.hasLost()) {
						os.writeInt(protocol.GAME_LOSE);
						os.writeInt(protocol.GAME_ENDED);
						break;
					}
					if(theGame.isSolved()) {
						os.writeInt(protocol.GAME_WIN);
						os.writeInt(protocol.GAME_ENDED);
						break;
					}
					os.writeInt(protocol.TURN_END);
				}
			} catch (IOException e) {
				e.printStackTrace();
//					int response = is.readInt();
//					if(response == protocol.CLIENT_CLOSING)
//					{
//					test = false;
//						close();
//					}
			}

	}
	
	/**
	 * Prompt pre-game setup, login and register. then run game.
	 */
	public void run()
	{
		while(test)
		{
			try 
			{
				int response = is.readInt();
				System.out.println("protocol." + response);
				
				try {
					if(response == protocol.ATTEMPT_LOGIN)
					{
						attemptLogin();
					} else if(response == protocol.ATTEMPT_REGISTER) {
						attemptRegister();
					}
				} catch (ServerException e) {
					System.out.println("Generic error encountered.");
					if(e.getErrorMode() == errors.LOGINERROR || e.getErrorMode() == errors.REGISTERERROR) {
						System.out.println("login or register failed, continuing");
						continue;
					} else {
						close();
						return;
					}
				}
				
				/*
				
				if(mode == status.LOGIN_SUCCESS || mode == status.REGISTER_SUCCESS) {
					runGame();
				} 
				
				*/
				
				if(response == protocol.ATTEMPT_START_GAME)
				{
					runGame();
				}
				
				if(response == protocol.CLIENT_CLOSING)
				{
					test = false;
					close();
				}
			} 
			catch (IOException e) 
			{
				e.printStackTrace();
				try 
				{
					test = false;
					close();
				} 
				catch (IOException e1) 
				{
					e1.printStackTrace();
				}
			}
			
		}
		
		try 
		{
			close();
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	
	}
	
	/**
	 * Get socket IO objects
	 * @throws IOException
	 */
	public void open() throws IOException
	{
		is = new DataInputStream(socket.getInputStream());
		os = new DataOutputStream(socket.getOutputStream());
	}
	
	/**
	 * Shut down thread.
	 * @throws IOException
	 */
	public void close() throws IOException
	{
		is.close();
		os.close();
		socket.close();
	}
}
